package top.yehot.kettle.boot.service.impl;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.quartz.JobDataMap;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;

import lombok.AllArgsConstructor;
import top.yehot.cmm.util.core.base.BaseException;
import top.yehot.cmm.util.core.utils.StrUtil;
import top.yehot.cmm.util.mybatis.utils.PageUtil;
import top.yehot.kettle.boot.core.SysConst;
import top.yehot.kettle.boot.core.utils.QuartzUtil;
import top.yehot.kettle.boot.enums.KettleResEnum;
import top.yehot.kettle.boot.enums.TaskStatusEnum;
import top.yehot.kettle.boot.mapper.GlobalParamMapper;
import top.yehot.kettle.boot.mapper.TaskMapper;
import top.yehot.kettle.boot.models.dto.TaskDto;
import top.yehot.kettle.boot.models.entity.GlobalParam;
import top.yehot.kettle.boot.models.entity.Task;
import top.yehot.kettle.boot.models.vo.task.TaskAddVO;
import top.yehot.kettle.boot.models.vo.task.TaskEditVO;
import top.yehot.kettle.boot.models.vo.task.TaskParamVO;
import top.yehot.kettle.boot.models.vo.task.TaskSearchVO;
import top.yehot.kettle.boot.models.vo.task.TaskVO;
import top.yehot.kettle.boot.service.TaskService;

/**
 * 任务 服务实现类
 * 
 * @author lquan
 * @date 2022年6月5日 上午9:26:56
 */
@Service
@AllArgsConstructor
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {
	
	private final QuartzUtil quartzUtil;
	private final String TASK_PREFIX = "TK";
	private final GlobalParamMapper globalParamMapper;

	@Override
	public IPage<TaskVO> page(TaskSearchVO param) {
		return baseMapper.page(PageUtil.of(param), param);
	}

	@Transactional
	@Override
	public void add(TaskAddVO param) {
		// 拷贝对象
		Task task = new Task();
		BeanUtils.copyProperties(param, task);
		// 验证并设置cron
		validCron(task, param.getCron());
		// 保存
		baseMapper.insert(task);
		
		// quartz名称
		task.setTaskName(TASK_PREFIX + StrUtil.fillZero(5, task.getId()));
		// 更新
		baseMapper.updateById(task);
		
		// 保存任务对应参数
		Optional.ofNullable(param.getParams()).ifPresent(tparams -> {
			tparams.stream().forEach(p -> {
				GlobalParam gpm = new GlobalParam();
				BeanUtils.copyProperties(p, gpm);
				gpm.setCmmv(task.getId().toString());
				globalParamMapper.insert(gpm);
			});
		});
	}

	@Override
	public TaskEditVO findById(Integer id) {
		// 查询任务对应参数
		List<GlobalParam> gplist = globalParamMapper.selectList(Wrappers.lambdaQuery(GlobalParam.class).eq(GlobalParam::getCmmv, id.toString()));
		
		TaskEditVO result = new TaskEditVO();
		BeanUtils.copyProperties(baseMapper.selectById(id), result);
		
		// 封装任务对应参数
		if (CollectionUtils.isEmpty(gplist)) {
			result.setParams(new ArrayList<>());
		} else {
			result.setParams(
				gplist.stream().map(gp -> new TaskParamVO().setPkey(gp.getPkey()).setPvalue(gp.getPvalue())).collect(Collectors.toList())
			);
		}
		return result;
	}

	@Transactional
	@Override
	public void edit(TaskEditVO param) {
		Task oldTask = baseMapper.selectById(param.getId());
		if (TaskStatusEnum.stop != oldTask.getStatus()) {
			throw new BaseException(KettleResEnum.TASK_NOT_MODIFY);
		}
		
		// 拷贝对象
		Task task = new Task();
		BeanUtils.copyProperties(param, task);
		// 验证并设置cron
		validCron(task, param.getCron());
		// 更新
		baseMapper.updateById(task);
		
		// 删除任务对应参数
		globalParamMapper.delete(Wrappers.lambdaQuery(GlobalParam.class).eq(GlobalParam::getCmmv, param.getId().toString()));
		// 保存任务对应参数
		Optional.ofNullable(param.getParams()).ifPresent(tparams -> {
			tparams.stream().forEach(p -> {
				GlobalParam gpm = new GlobalParam();
				BeanUtils.copyProperties(p, gpm);
				gpm.setCmmv(param.getId().toString());
				globalParamMapper.insert(gpm);
			});
		});
	}

	@Override
	public void delete(Integer id) {
		Task task = baseMapper.selectById(id);
		if (TaskStatusEnum.stop != task.getStatus()) {
			throw new BaseException(KettleResEnum.TASK_UNABATED_NOT_DELETE);
		}
		
		task.setStatus(TaskStatusEnum.delete);
		baseMapper.updateById(task);
	}

	@Transactional
	@Override
	public void start(Integer id) {
		Task task = baseMapper.selectById(id);
		if (TaskStatusEnum.stop != task.getStatus()) {
			throw new BaseException(KettleResEnum.TASK_RUN_STATUS);
		}
		
		// 设置属性
		task.setStatus(TaskStatusEnum.run);
		// 更新数据库状态
		baseMapper.updateById(task);
		
		JobDataMap jobMap = new JobDataMap();
		jobMap.put(SysConst.TASK_ID, task.getId());
		// 启动任务和触发器
		quartzUtil.addCronJob(TaskDto.packageDto(task.getTaskName(), task.getCron(), jobMap));
	}

	@Transactional
	@Override
	public void stop(Integer id) {
		Task task = baseMapper.selectById(id);
		if (TaskStatusEnum.run != task.getStatus()) {
			throw new BaseException(KettleResEnum.TASK_STOP_STATUS);
		}
		
		// 设置属性
		task.setStatus(TaskStatusEnum.stop);
		// 更新数据库状态
		baseMapper.updateById(task);
		
		// 删除任务和触发器
		quartzUtil.removeJob(task.getTaskName());
	}

	@Override
	public List<String> parseCron(String cron) {
		List<String> resultList = new ArrayList<String>();
		try {
			// Defines fields and conditions over each field for a cron
			CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
			// cron解析器
			CronParser parser = new CronParser(cronDefinition);
			// cron执行器
			ExecutionTime excuExecutionTime = ExecutionTime.forCron(parser.parse(cron));
			
			// 最近六次执行时间
			Optional<ZonedDateTime> zdt = null;
			for (int i = 0; i < 6; i++) {
				if (i == 0) {
					zdt = excuExecutionTime.nextExecution(ZonedDateTime.now());
				} else {
					zdt = excuExecutionTime.nextExecution(zdt.get());
				}
				resultList.add(
					zdt.get().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
				);
			}
		} catch (Exception e) {
			log.error(KettleResEnum.CRON_ERR.getMsg(), e);
			throw new BaseException(KettleResEnum.CRON_ERR);
		}
		return resultList;
	}
	
	/**
	 * 验证cron表达式
	 * 
	 * @param task 任务对象
	 * @param cron cron表达式
	 */
	private void validCron(Task task, String cron) {
		cron = cron.trim();
		parseCron(cron);
		task.setCron(cron);
	}

}
